import { waitFor } from '@testing-library/react';
import { beforeEach, describe, expect, it, vi } from 'vitest';

import { bindCreateFixtures } from '@/test/create-fixtures';
import { render } from '@/test/utils';

import { clearFetchCache } from '../../../hooks';
import { UserVerificationFactorTwo } from '../UserVerificationFactorTwo';

const { createFixtures } = bindCreateFixtures('UserVerification');

describe('UserVerificationFactorTwo', () => {
  /**
   * `<UserVerificationFactorOne/>` internally uses useFetch which caches the results, be sure to clear the cache before each test
   */
  beforeEach(() => {
    clearFetchCache();
  });

  it('renders the component for with strategy:phone_code', async () => {
    const { wrapper, fixtures } = await createFixtures(f => {
      f.withUser({ username: 'clerkuser' });
    });
    vi.spyOn(fixtures.session, 'startVerification').mockResolvedValue({
      status: 'needs_second_factor',
      supportedSecondFactors: [{ strategy: 'phone_code' }],
    } as any);

    vi.spyOn(fixtures.session, 'prepareSecondFactorVerification').mockResolvedValue({
      status: 'needs_second_factor',
      supportedSecondFactors: [{ strategy: 'phone_code' }],
    } as any);

    const { findByText, getAllByTestId } = render(<UserVerificationFactorTwo />, { wrapper });

    await findByText('Verification required');
    const inputs = getAllByTestId('otp-input-segment');
    expect(inputs.length).toBe(6);
  });

  it('renders the component for with strategy:totp', async () => {
    const { wrapper, fixtures } = await createFixtures(f => {
      f.withUser({ username: 'clerkuser' });
    });
    vi.spyOn(fixtures.session, 'startVerification').mockResolvedValue({
      status: 'needs_second_factor',
      supportedSecondFactors: [{ strategy: 'totp' }],
    } as any);

    vi.spyOn(fixtures.session, 'prepareSecondFactorVerification').mockResolvedValue({
      status: 'needs_second_factor',
      supportedSecondFactors: [{ strategy: 'totp' }],
    } as any);
    const { findByLabelText, findByText } = render(<UserVerificationFactorTwo />, { wrapper });

    await findByText('Verification required');
    await findByText('Enter the code generated by your authenticator app to continue');
    await findByLabelText(/Enter verification code/i);
  });

  it('renders the component for with strategy:backup_code', async () => {
    const { wrapper, fixtures } = await createFixtures(f => {
      f.withUser({ username: 'clerkuser' });
    });
    vi.spyOn(fixtures.session, 'startVerification').mockResolvedValue({
      status: 'needs_second_factor',
      supportedSecondFactors: [{ strategy: 'backup_code' }],
    } as any);

    vi.spyOn(fixtures.session, 'prepareSecondFactorVerification').mockResolvedValue({
      status: 'needs_second_factor',
      supportedSecondFactors: [{ strategy: 'backup_code' }],
    } as any);
    const { findByLabelText, findByText } = render(<UserVerificationFactorTwo />, { wrapper });

    await findByText('Enter a backup code');
    await findByText('Enter the backup code you received when setting up two-step authentication');
    await findByLabelText(/Backup code/i);
  });

  describe('Navigation', () => {
    it('navigates to UserVerificationFactorOne component if user lands on SignInFactorTwo page but they should not', async () => {
      const { wrapper, fixtures } = await createFixtures(f => {
        f.withUser({ username: 'clerkuser' });
      });
      vi.spyOn(fixtures.session, 'startVerification').mockResolvedValue({
        status: 'needs_first_factor',
      } as any);
      render(<UserVerificationFactorTwo />, { wrapper });

      await waitFor(() => expect(fixtures.router.navigate).toHaveBeenCalledWith('../'));
    });
  });

  describe('Submitting', () => {
    it('sets an active session when user submits second factor successfully', async () => {
      const { wrapper, fixtures } = await createFixtures(f => {
        f.withUser({ username: 'clerkuser' });
      });
      vi.spyOn(fixtures.session, 'startVerification').mockResolvedValue({
        status: 'needs_second_factor',
        supportedSecondFactors: [{ strategy: 'phone_code' }],
      } as any);

      vi.spyOn(fixtures.session, 'prepareSecondFactorVerification').mockResolvedValue({
        status: 'needs_second_factor',
        supportedSecondFactors: [{ strategy: 'phone_code' }],
      } as any);

      vi.spyOn(fixtures.session, 'attemptSecondFactorVerification').mockResolvedValue({
        status: 'complete',
        supportedSecondFactors: [],
        session: {
          id: '123',
        },
      } as any);

      const { userEvent, getByLabelText, findByText } = render(<UserVerificationFactorTwo />, { wrapper });

      await findByText('Verification required');

      await userEvent.type(getByLabelText(/Enter verification code/i), '123456');
      await waitFor(() => {
        expect(fixtures.clerk.setActive).toHaveBeenCalled();
      });
    });
  });

  describe('Use another second factor method', () => {
    it('should list enabled second factor methods without the current one', async () => {
      const { wrapper, fixtures } = await createFixtures(f => {
        f.withUser({ username: 'clerkuser' });
      });
      vi.spyOn(fixtures.session, 'startVerification').mockResolvedValue({
        status: 'needs_second_factor',
        supportedSecondFactors: [
          {
            strategy: 'phone_code',
            phoneNumberId: 'phone_1',
            safeIdentifier: '+3069XXXXXXX1',
          },
          {
            strategy: 'phone_code',
            phoneNumberId: 'phone_2',
            safeIdentifier: '+3069XXXXXXX2',
          },
        ],
      } as any);
      vi.spyOn(fixtures.session, 'prepareSecondFactorVerification').mockResolvedValue({} as any);

      const { userEvent, findByText, getByText, findByRole } = render(<UserVerificationFactorTwo />, {
        wrapper,
      });

      await findByText('Verification required');
      await findByText('Use another method');

      await userEvent.click(getByText('Use another method'));
      const button = await findByRole('button');
      expect(button).toHaveTextContent('Send SMS code to +3069XXXXXXX2');
      expect(button).not.toHaveTextContent('Send SMS code to +3069XXXXXXX1');
    });

    it('can select another method', async () => {
      const { wrapper, fixtures } = await createFixtures(f => {
        f.withUser({ username: 'clerkuser' });
      });
      vi.spyOn(fixtures.session, 'startVerification').mockResolvedValue({
        status: 'needs_second_factor',
        supportedSecondFactors: [
          {
            strategy: 'phone_code',
            phoneNumberId: 'phone_1',
            safeIdentifier: '+3069XXXXXXX1',
          },
          {
            strategy: 'phone_code',
            phoneNumberId: 'phone_2',
            safeIdentifier: '+3069XXXXXXX2',
          },
        ],
      } as any);
      vi.spyOn(fixtures.session, 'prepareSecondFactorVerification').mockResolvedValue({} as any);

      const { userEvent, findByText, getByText, container } = render(<UserVerificationFactorTwo />, { wrapper });

      await findByText('Verification required');
      expect(container).toHaveTextContent('+3069XXXXXXX1');
      expect(container).not.toHaveTextContent('+3069XXXXXXX2');
      await findByText('Use another method');

      await userEvent.click(getByText('Use another method'));
      await userEvent.click(getByText('Send SMS code to +3069XXXXXXX2'));

      await findByText('Verification required');
      expect(container).toHaveTextContent('+3069XXXXXXX2');
      await findByText('Use another method');

      await userEvent.click(getByText('Use another method'));
      await waitFor(() => {
        expect(container).toHaveTextContent('+3069XXXXXXX1');
      });
    });
  });

  describe('Get Help', () => {
    it.todo('should render the get help component when clicking the "Get Help" button');
  });
});
